package com.xihuanjun.sdk.dao;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xihuanjun.sdk.annotation.UniqueKey;
import com.xihuanjun.sdk.config.SapSLConfig;
import com.xihuanjun.sdk.entity.*;
import com.xihuanjun.sdk.enums.HttpMethod;
import com.xihuanjun.sdk.enums.SapMethodType;
import com.xihuanjun.sdk.enums.SapResultEnum;
import com.xihuanjun.sdk.enums.SapSLMethod;
import com.xihuanjun.sdk.exception.QuantityNotSufficientException;
import com.xihuanjun.sdk.exception.SapException;
import com.xihuanjun.sdk.exception.TimedOutException;
import com.xihuanjun.sdk.exception.TooManyResultException;
import com.xihuanjun.sdk.util.HttpUtils;
import com.xihuanjun.sdk.util.JsonUtils;
import com.xihuanjun.sdk.util.PublicReflectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.*;

/**
 * Copyright (C), 2024-08-24
 * FileName: BaseDao
 * Author:   溪浣君
 * Date:     2024-08-24 9:40
 * Description: SAP单据操作基类
 */
@Slf4j
public abstract class SapB1SLBaseDao {

    protected HttpUtils httpUtils;
    private SapSLConfig sapSLConfig;

    private static String token;

    protected SapSLMethod slMethod;

    //用户自定义表名，当slMethod==UserObject时使用
    protected  String userObjectName;


    // region 基础属性

    /**
     * 子类必须设置方法类型
     */
    protected abstract void setSlMethod();

    public SapB1SLBaseDao() {
        setSlMethod();
    }

    /**
     * 为子类自动注入httpUtils。子类可直接使用
     * @param httpUtils
     */
    @Autowired
    public final void setHttpUtils(HttpUtils httpUtils) {
        this.httpUtils=httpUtils;
    }

    @Autowired
    public final void setSapSLConfig(SapSLConfig sapSLConfig) {
        this.sapSLConfig=sapSLConfig;
    }


    // endregion


    // region 公开方法

    /**
     * 根据主键获取单据对象的通用方法
     * @param primaryKey 主键
     * @param tClass
     * @return
     * @param <T>
     */
    public <T> T getObjectByPrimaryKey(String primaryKey,Class<T> tClass){
        String sapMethod = getSapMethod(slMethod, SapMethodType.GetOne, primaryKey);
        SapRequestResult requestResult = this.sendSapRequest(sapMethod, HttpMethod.GET, null, null);
        System.out.println("JSON:"+requestResult.getJson().replaceAll("(?<=\\{)\\s+(?=\\\")|(?<=\\\")\\s+(?=:)|(?<=:)\\s+(?=\\\")|(?<=,)\\s+(?=\\\")|(?<=:)\\s+|\\n",""));
         T t = JsonUtils.json2Object(requestResult.getJson(), tClass);
        return  t;
    }



    /**
     * 根据条件获取单据主键，只返回查到的第一个值，营销单据返回DOcEntry,凭证返回JdtNum
     * @param condition 请设置$filter
     * @return
     */
    public String getPrimaryKey(Map condition){
        return getPrimaryKey(condition,false);
    }

    /**
     * 根据条件查询单据的主键
     * @param condition 条件，需要$filter
     * @param isTooManyException 查到了多个结果是否抛出异常，如果false则返回第一个结果的主键
     * @return
     */
    public String getPrimaryKey(Map condition,boolean isTooManyException){
        String sapMethod = getSapMethod(slMethod, SapMethodType.GetList, null);
        SapRequestResult requestResult = this.sendSapRequest(sapMethod, HttpMethod.GET, condition, null);

        String itemsJsonString = getValueJsonString(requestResult.getJson());
        ObjectMapper mapper=new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
        List<BaseEntity> sapItems = null;
        try {
            sapItems = mapper.readValue(itemsJsonString, new TypeReference<List<BaseEntity>>() {
            });
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        if(sapItems!=null && sapItems.size()>0){
            if(sapItems.size()>1 && isTooManyException){
                throw new TooManyResultException("查到了多个结果："+sapItems.size());
            }
            BaseEntity entity=sapItems.get(0);
            return getPrimaryKeyByMethod(slMethod,entity);
        }
        return null;
    }

    /**
     * 分页条件查询
     * @param condition $filter里写筛选条件,$select里设定返回字段，$orderby设定排序列，如果都没有可以传null
     * @param clazz 解析成的子类型
     * @param pageIndex 页码
     * @param pageSize 每页条数
     * @return
     * @param <T>
     */
    public <T extends BaseEntity> List<T> getList(Map condition,Class<T> clazz,int pageIndex,int pageSize){
        this.addPageParam(condition,pageIndex,pageSize);
        String sapMethod = getSapMethod(this.slMethod, SapMethodType.GetList, null);
        SapRequestResult requestResult = this.sendSapRequest(sapMethod, HttpMethod.GET, condition, null);
        String valueJsonString = this.getValueJsonString(requestResult.getJson());
        ObjectMapper mapper=new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
        try {
            JavaType javaType = mapper.getTypeFactory().constructParametricType(List.class, clazz);
            List<T> ts = mapper.readValue(valueJsonString, javaType);
            return ts;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    /**
     * 新增单据通用方法，营销单据返回DocEntry，凭证返回JdtNum
     * @param entity
     * @return
     */
    public String addObject(BaseEntity entity){
        String json = JsonUtils.object2Json(entity);
        return addObject(json,entity);
    }

    public <T extends SapOrder> String addObject(String json,Class<T> clazz){
        T t = JsonUtils.json2Object(json, clazz);
        return addObject(json,t);
    }

    /**
     * 根据json字符串新增单据
     * @param json 新增用的json字符串
     * @param sapOrder  超时重试时使用
     * @return
     */
    public String addObject(String json,BaseEntity sapOrder){
        String sapMethod = getSapMethod(slMethod, SapMethodType.Save, null);
        SapRequestResult requestResult = this.sendSapRequest(sapMethod, HttpMethod.POST, null, json);
        if(requestResult.getResultEnum()==SapResultEnum.READ_TIMED_OUT){
            log.debug("请求超时");
            String uniqueKeyCondition = getUniqueKeyCondition(sapOrder);
            if(StringUtils.hasText(uniqueKeyCondition)){
                log.debug("唯一查询条件：{}",uniqueKeyCondition);
                Map map=new HashMap();
                map.put("$filter",uniqueKeyCondition);
                return getPrimaryKey(map);
            }else{
                log.debug("没有找到唯一键设置,或唯一值为null，无法查询是否新增成功");
                throw new TimedOutException("调用超时");
            }
        }else{//没有调用超时，直接根据返回的json确定主键
            if(StringUtils.hasText(requestResult.getJson())){
                BaseEntity baseEntity = JsonUtils.json2Object(requestResult.getJson(), BaseEntity.class);
                return getPrimaryKeyByMethod(slMethod,baseEntity);
            }else{
                return "";
            }
        }

    }

    /**
     * 取消一个单据
     * @param primaryKey
     * @param returns
     * @param <T>
     */
    public  void cancel(String primaryKey){

        String sapMethod = getSapMethod(this.slMethod, SapMethodType.Cancel, primaryKey);
        SapRequestResult requestResult = this.sendSapRequest(sapMethod, HttpMethod.POST, null, null);
        System.out.println(requestResult.getJson());
    }


    /**
     * 根据主键更新对象
     * @param entity
     */
    public void update(BaseEntity entity){
        String sapMethod = getSapMethod(slMethod, SapMethodType.Update, this.getPrimaryKeyByMethod(this.slMethod,entity));
        this.sendSapRequest(sapMethod, HttpMethod.PATCH, null, JsonUtils.object2Json(entity));
    }

    /**
     * 占用批次库存
     * @param sourceData 只用到物料编号和批次列表，源数据
     * @param itemCode 要占用的物料编号
     * @param qty 要占用的数量
     * @return 源数据中分配的批次数据
     */
    public <T extends SapOrderItem> List<SapOrderItemBatch> occupiedStock(SapOrder<T> sourceData, String itemCode, BigDecimal qty) throws QuantityNotSufficientException{
        log.debug("开始匹配批次，物料{},数量{}",itemCode,qty);
        List<SapOrderItemBatch> listReturn=new ArrayList<>();
        for (T item : sourceData.getDocumentLines()) {
            if(item.getItemCode().equals(itemCode)){//找到物料
                List<SapOrderItemBatch> batchNumbers = item.getBatchNumbers();
                log.debug("从源数据找到了该物料，批次数量：{}",batchNumbers.size());
                for (SapOrderItemBatch batchNumber : batchNumbers) {
                    log.debug(MessageFormat.format("批次{0}，数量{1}，当前需求{2}",batchNumber.getBatchNumber(),batchNumber.getQuantity(),qty));
                    if(batchNumber.getQuantity().compareTo(qty)>=0){//批次够用
                        log.debug("批次数量够用，直接返回");
                        batchNumber.setQuantity(batchNumber.getQuantity().subtract(qty));
                        SapOrderItemBatch batch=new SapOrderItemBatch();
                        batch.setBatchNumber(batchNumber.getBatchNumber());
                        batch.setQuantity(qty);
                        listReturn.add(batch);
                        return listReturn;
                    }else{//当前批次不够用
                        log.debug("批次数量不够用，把该批次所有数量都扣掉");
                        batchNumber.setQuantity(new BigDecimal("0"));
                        SapOrderItemBatch batch=new SapOrderItemBatch();
                        batch.setBatchNumber(batchNumber.getBatchNumber());
                        batch.setQuantity(batchNumber.getQuantity());
                        listReturn.add(batch);
                        //更新需求量
                        qty=qty.subtract(batch.getQuantity());
                    }
                }
            }
        }
        log.debug("剩余需求量：{}",qty);
        if(qty.compareTo(BigDecimal.ZERO)>0){
            throw new QuantityNotSufficientException(MessageFormat.format("{0}剩余未分配数量{1}",itemCode,qty));
        }
        return listReturn;
    }

    // endregion


    /**
     * 获取调用SAP接口的方法名
     * @param slMethod
     * @param type
     * @param primaryKey
     * @return
     */
    private String getSapMethod(SapSLMethod slMethod, SapMethodType type,String primaryKey){
        StringBuilder sb=new StringBuilder();
        if(slMethod!=SapSLMethod.UserObject){
            sb.append(slMethod.toString());
            if(slMethod==SapSLMethod.Drafts&& type==SapMethodType.Draft2Document){//新增草稿需要特殊处理
                sb.append("Service_SaveDraftToDocument");
            }
        }else{
            if(StringUtils.hasText(userObjectName)==false){
                throw new SapException("用户自定义表操作尚未设置表名，请先调用setUserObjectName()方法");
            }
            sb.append(userObjectName);
        }
        if(type!=SapMethodType.Save&& type!= SapMethodType.GetList){//不是新增和查多条，都需要用到主键
            sb.append("(");
            switch (slMethod){
                case Items:
                case BusinessPartners://如果是物料主数据和业务伙伴，则查询加上单引号
                    sb.append("'");
                    sb.append(primaryKey);
                    sb.append("'");
                    break;
                default:
                    sb.append(primaryKey);
            }
            sb.append(")");
        }
        switch (type){
            case Cancel:
            case Close:
            case Reopen:
                sb.append("/");
                sb.append(type.toString());
            case Update:
                if(StringUtils.hasText(primaryKey)==false)
                    throw new SapException("新增和修改必须提供主键");
        }
        return sb.toString();
    }

    /**
     * 根据不同的方法返回对应的主键数据
     * @param slMethod
     * @param entity
     * @return
     */
    private String getPrimaryKeyByMethod(SapSLMethod slMethod,BaseEntity entity){
        switch (slMethod){
            case Items:
                return entity.getItemCode();
            case BusinessPartners:
                return entity.getCardCode();
            case JournalEntries:
                return entity.getJdtNum().toString();
            case ProductionOrders:
                return entity.getAbsoluteEntry().toString();
            case EmployeesInfo:
                return entity.getEmployeeID().toString();
            case JournalVouchersService_Add:
                return "";
            default:
                return entity.getDocEntry().toString();
        }
    }


    /**
     * 获取单据的唯一值列数据，拼成可以查询的字符串
     * 新增超时后调用
     * @param entity
     * @return
     */
    public String getUniqueKeyCondition(BaseEntity entity){
        StringBuilder stringBuilder=new StringBuilder();
        List<Field> declaredFields = getAllFields(entity);
        for (Field field:declaredFields) {
            //判断属性上是否加了UniqueKey这个注解
            if (field.isAnnotationPresent(UniqueKey.class)) {
                UniqueKey annotation = field.getAnnotation(UniqueKey.class);
                SapSLMethod[] methods = annotation.value();
                if(containMethod(methods)){
                    String s = field.getName().toString();
                    System.out.println("属性："+s);
                    stringBuilder.append(s);
                    stringBuilder.append(" eq '");
                    Object fieldValueByName = PublicReflectionUtils.getFieldValueByName(s, entity);
                    System.out.println(s+"属性的值:"+fieldValueByName);
                    if(fieldValueByName==null){
                        return null;
                    }
                    stringBuilder.append(fieldValueByName);
                    stringBuilder.append("'");
                    break;
                }

            }
        }
        return stringBuilder.toString();
    }

    private List<Field> getAllFields(BaseEntity entity){
        List<Field> list=new ArrayList<>();
        Class aClass = entity.getClass();
        while(aClass!=null){
            Field[] declaredFields = aClass.getDeclaredFields();
            Collections.addAll(list,declaredFields);
            aClass=aClass.getSuperclass();
        }
        return list;
    }

    /**
     * 判定一个方法数组里是否有当前方法
     * @param values
     * @return
     */
    private boolean containMethod(SapSLMethod[] values){
        for (SapSLMethod value : values) {
            if(value==this.slMethod){
                return true;
            }
        }
        return false;
    }


     /**
     * 根据sap返回的原json，获取value里的字符串，获取后可直接转成对象集合
     * @param jsonResult
     * @return
     */
     protected String getValueJsonString(String jsonResult){
        HashMap map = JsonUtils.json2map(jsonResult);
        ArrayList<LinkedHashMap> value =(ArrayList<LinkedHashMap>) map.get("value");
        return JsonUtils.object2Json(value);
     }

    /**
     * 为查询参数的map增加分页参数
     * @param targetMap 要查询的map参数
     * @param pageIndex 第几页
     * @param pageSize 每页多少条
     */
     protected void  addPageParam(Map targetMap,int pageIndex,int pageSize){
         if(targetMap==null){
             targetMap=new HashMap();
         }
         targetMap.put("$top",pageSize);
         int skip=(pageIndex-1)*pageSize;
         targetMap.put("$skip",skip);
     }


    /**
     * 发送一个SAP请求，会按配置自动登录，如果session过期会刷新session后重试
     * @param method SAP接口方法
     * @param httpMethod 请求类型
     * @param urlParam url参数
     * @param jsonParam json参数
     * @return 返回我
     * @throws JsonProcessingException
     */
    protected SapRequestResult sendSapRequest(String method, HttpMethod httpMethod, Map<String,Object> urlParam, String jsonParam) {
        SapRequestResult requestResult=SapRequestResult.result(SapResultEnum.OK);
        log.info("发送SAP请求{}，url参数{}",method,urlParam);
        log.info("json参数：{}",jsonParam);
        //LogEvent.doLog(logEvent, MessageFormat.format("发送{2}请求{0}，url参数：{1}，json参数：{3}",method,urlParam,httpMethod,jsonParam));
        String result=null;
        //cookie数据，不是登录都要加
        Map<String,String> cookies=null;
        if(method.equals("Login")==false){
            cookies=new HashMap<>();
            cookies.put("B1SESSION",token);
        }
        //确定请求地址
        String url=sapSLConfig.getServerPath()+method;
        if(urlParam!=null){//get请求的url参数
            url+="?";
            Iterator<Map.Entry<String, Object>> iterator = urlParam.entrySet().iterator();
            while(iterator.hasNext()){
                Map.Entry<String, Object> next = iterator.next();
                url+=next.getKey()+"="+ URLEncoder.encode(next.getValue().toString()).replaceAll("\\+", "%20")+"&";
            }
            url=url.substring(0,url.length()-1);
        }
        try {
            switch (httpMethod){
                case GET:
                    if(urlParam!=null && urlParam.containsKey("$top")){//有分页，需要把每页条数增加到header里
                        Map header=new HashMap();
                        header.put("Prefer","odata.maxpagesize="+urlParam.get("$top"));
                        result=httpUtils.sendGet(url,cookies,header);
                    }else{
                        result= httpUtils.sendGet(url, cookies,null);
                    }
                    break;
                case POST:
                    result=httpUtils.sendPost(url,cookies,jsonParam);
                    break;
                case PATCH:
                    result = httpUtils.sendPatch(url,cookies,jsonParam);
                    break;
            }
            if(validate(result)== SapResultEnum.SESSION_ERROR){//如果是session过期，则刷新token重试
                log.debug("session过期，刷新token");
                refreshToken();
                return sendSapRequest(method,httpMethod,urlParam,jsonParam);
            }
            //log.debug("SAP请求结果：{}",result);
            requestResult.setJson(result);
        } catch (SocketTimeoutException e) {
            e.printStackTrace();
            log.debug("请求异常："+e.getMessage());
            requestResult.setResultEnum(SapResultEnum.READ_TIMED_OUT);
        } catch (IOException e) {
            e.printStackTrace();
            log.debug("请求异常："+e.getMessage());
            requestResult.setResultEnum(SapResultEnum.READ_TIMED_OUT);
        }
        return requestResult;
    }

    private void refreshToken()  {
        log.debug("开始刷新token");
        Map<String,Object> map=new HashMap<>();
        map.put("CompanyDB",sapSLConfig.getCompanyDataBaseName());
        map.put("UserName",sapSLConfig.getUserName());
        map.put("Password",sapSLConfig.getPassword());
        SapRequestResult requestResult = sendSapRequest("Login", HttpMethod.POST, null, JsonUtils.map2json(map));
        if(validate(requestResult.getJson())== SapResultEnum.OK){
            HashMap result = JsonUtils.json2map(requestResult.getJson());
            this.token =  result.get("SessionId").toString();
            log.debug("token已刷新：{}",this.token);
        }
    }

    /**
     * 根据返回的json数据验证sap接口返回的结果类型
     * @param resultJson sap返回的原始数据
     * @return
     * @throws JsonProcessingException
     */
    private SapResultEnum validate(String resultJson) throws SapException {
        if(resultJson!=null &&resultJson.equals("")){//发送PATCH请求成功后，会返回""
            return SapResultEnum.OK;
        }
        ObjectMapper mapper=new ObjectMapper();
        Map<String, Object> map = null;
        try {
            map = mapper.readValue(resultJson, Map.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        if(map.containsKey("error")){
            //获取code和message
            String code,message;
            LinkedHashMap lhm=(LinkedHashMap)(map.get("error"));
            code =lhm.get("code").toString();
            LinkedHashMap messageMap =(LinkedHashMap)lhm.get("message");
            message = messageMap.get("value").toString();
            if(code.equals("301") && message.equals("Invalid session or session already timeout.")){
                return SapResultEnum.SESSION_ERROR;
            }
            if(code.equals("-2028") && message.equals("No matching records found (ODBC -2028)")){
                return SapResultEnum.NO_MATCH_DATA;
            }

            if(code.equals("100000027") && message.equals("Login failed")){
                //return SapResultEnum.LOGIN_FAILED;
                throw new SapException("无法登录，请检查登录信息是否正确");
            }
            if(code.equals("-10")){//负库存
                throw new SapException("sap错误："+message);
            }
            if(code.equals("100000027") && message.startsWith("Error while connecting to database")){
                //return SapResultEnum.SERVER_ERROR;
                throw new SapException("sap错误："+message);
            }
            log.debug("访问sap错误：{}",message);
            throw new SapException("sap错误："+message);
        }else{
            return SapResultEnum.OK;
        }
    }
}
